//------------------------------------------------------------------------------
// Copyright (c) 2008 www.dnaide.com
// Licensed under the MIT (MIT-LICENSE.txt)
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Globalization;
using System.IO;
using System.Configuration;

using DNAide.Core;
using DNAide.Configuration;

namespace DNAide.Web
{
	/// <summary>
	/// </summary>
	public class UrlRewriter : IHttpModule
	{

		#region IHttpModule Members

		/// <summary></summary>
		public void Dispose()
		{
		}

		/// <summary></summary>
		public void Init(HttpApplication context)
		{
			if (context == null)
			{
				// Do nothing
				return;
			}

			if (DNAideConfigurationSettings.Web.UrlRewriter.FiresOnEvent == HttpModuleFiringEvent.None)
			{
				// Do nothing
				return;
			}

			// Set the event handler
			switch (DNAideConfigurationSettings.Web.UrlRewriter.FiresOnEvent)
			{
				case HttpModuleFiringEvent.BeginRequest:
					context.BeginRequest += new EventHandler(Application_BeginRequest);
					break;
				case HttpModuleFiringEvent.AuthenticateRequest:
					context.AuthenticateRequest += new EventHandler(Application_AuthenticateRequest);
					break;
				case HttpModuleFiringEvent.AuthorizeRequest:
					context.AuthorizeRequest += new EventHandler(Application_AuthorizeRequest);
					break;
			}

			context.PreRequestHandlerExecute += new EventHandler(Application_PreRequestHandlerExecute);
		}

		#endregion

		/// <summary>
		/// The SiteMapProvider used by the UrlRewriter.
		/// </summary>
		public static SiteMapProvider CurrentSiteMapProvider
		{
			get
			{
				if (!IsSiteMapActive)
					return null;

				SiteMapProvider retval;
				if (!HttpContext.Current.Items.Contains("DNAideCurrentSiteMapProvider"))
				{
					retval = SiteMap.Provider;
				}
				else
				{
					retval = (SiteMapProvider)HttpContext.Current.Items["DNAideCurrentSiteMapProvider"];
				}
				return retval;
			}
			set
			{
				HttpContext.Current.Items["DNAideCurrentSiteMapProvider"] = value;
			}
		}


		/// <summary></summary>
		public static Boolean IsSiteMapActive
		{
			get
			{
				return SiteMap.Enabled;
			}
		}

		private static void HandleRequest(HttpApplication app)
		{
			string requestedPath = app.Request.Path;

			bool appendedDefaultPage = false;
			if (requestedPath.EndsWith("/"))
			{
				requestedPath = String.Concat(requestedPath, DNAideConfigurationSettings.Web.UrlRewriter.DefaultPage);
				appendedDefaultPage = true;
			}
			else if (Path.GetExtension(requestedPath).Length < 1)
			{	
				// A directory has been requested (but "/" has not been appended)
				requestedPath = String.Concat(requestedPath, "/", DNAideConfigurationSettings.Web.UrlRewriter.DefaultPage);
				appendedDefaultPage = true;
			}
			if (appendedDefaultPage)
			{
				app.Context.Items.Add("DNAideUrlRewriterAppendedDefaultPage", true);
				HttpContext.Current.RewritePath(requestedPath);
			}

			string fileExtension = Path.GetExtension(app.Request.Path).ToString();

			if (!CheckApplicableFileExtension(fileExtension))
			{
				// Not an applicable file extension so do nothing
				return;
			}

			if (DNAideConfigurationSettings.Web.UrlRewriter.VirtualUrlsOnly 
				&& File.Exists(app.Request.MapPath(app.Request.Path)))
			{
				// File exists so do nothing
				return;
			}

			if (IsSiteMapActive)
				CurrentSiteMapProvider.SiteMapResolve += new SiteMapResolveEventHandler(SiteMap_SiteMapResolve);

			if (DNAideConfigurationSettings.Web.UrlRewriter.SearchPriority == UrlRewriterSearchPriorityType.Rules)
			{
				// Check UrlRewriter rules
				if (CheckRules(app))
				{
					// Rewrite or redirect has happened
					return;
				}
				// Check any rules against the current sitemapnode
				if (IsSiteMapActive && CheckCurrentSiteMapNode())
				{
					// Rewrite or redirect has happened
					return;
				}

			}
			else
			{
				// Check any rules against the current sitemapnode
				if (IsSiteMapActive && CheckCurrentSiteMapNode())
				{
					// Rewrite or redirect has happened
					return;
				}
				// Check UrlRewriter rules
				if (CheckRules(app))
				{
					// Rewrite or redirect has happened
					return;
				}
			}
			
		}

		static SiteMapNode SiteMap_SiteMapResolve(Object sender, SiteMapResolveEventArgs e)
		{
			if (CurrentSiteMapProvider.CurrentNode != null)
			{
				return CurrentSiteMapProvider.CurrentNode;
			}

			if (HttpContext.Current.Items.Contains("DNAideCurrentSiteMapNode"))
			{
				return (SiteMapNode)HttpContext.Current.Items["DNAideCurrentSiteMapNode"];
			}

			bool defaultPageAppended = CastAide.AsBoolean(HttpContext.Current.Items["DNAideUrlRewriterAppendedDefaultPage"], false);
			if (defaultPageAppended)
			{
				string requestedPath = HttpContext.Current.Request.RawUrl;

				// Strip off any query string before appending other path values
				if (!string.IsNullOrEmpty(requestedPath) && requestedPath.IndexOf("?") != -1)
					requestedPath = requestedPath.Substring(0, requestedPath.IndexOf("?"));

				if (requestedPath.EndsWith("/"))
				{
					requestedPath = String.Concat(requestedPath, DNAideConfigurationSettings.Web.UrlRewriter.DefaultPage);
				}
				else
				{
					// A directory has been requested (but "/" has not been appended)
					requestedPath = String.Concat(requestedPath, "/", DNAideConfigurationSettings.Web.UrlRewriter.DefaultPage);
				}

				SiteMapNode foundNode = CurrentSiteMapProvider.FindSiteMapNode(requestedPath);
				HttpContext.Current.Items["DNAideCurrentSiteMapNode"] = foundNode;
				return foundNode;
			}
		
			return null;
		}


		private static bool CheckApplicableFileExtension(string fileExtension)
		{
			bool retval = false;
			if (!String.IsNullOrEmpty(DNAideConfigurationSettings.Web.UrlRewriter.ApplicableFileExtensions))
			{
				// If it's and invalid file extension for the rewriter, then do nothing
				if (String.CompareOrdinal(DNAideConfigurationSettings.Web.UrlRewriter.ApplicableFileExtensions, ".*") == 0)
				{
					retval = true;
				}
				else
				{
					string[] applicableFileExtensions = DNAideConfigurationSettings.Web.UrlRewriter.ApplicableFileExtensions.Split(new char[] { ',' });
					foreach (string applicableFileExtension in applicableFileExtensions)
					{
						if (String.CompareOrdinal(applicableFileExtension.Trim().ToLower(), fileExtension.ToLower()) == 0)
						{
							retval = true;
							break;
						}
					}
				}
			}
			return retval;
		}


		private static bool CheckRules(HttpApplication app)
		{
			IEnumerator<UrlRewriterRuleConfig> ruleEnum = DNAideConfigurationSettings.Web.UrlRewriter.Rules.GetEnumerator();
			while (ruleEnum.MoveNext())
			{
				if (ruleEnum.Current.MatchExpressionRegex.IsMatch(app.Request.RawUrl))
				{
					if (ruleEnum.Current.DestinationType == UrlRewriterRuleDestinationType.Ignore)
					{
						return true;
					}

					string finalDestination = string.Empty;

					if (ruleEnum.Current.DestinationType == UrlRewriterRuleDestinationType.SiteMapNodeKey)
					{
						// Rewriting/redirecting to the Url of a SiteMapNode key
						string siteMapNodeKey = ruleEnum.Current.Destination;
						string queryStringToAppend = string.Empty;
						if (siteMapNodeKey.Contains("?"))
						{
							string[] siteMapNodeKeyArr = siteMapNodeKey.Split(new Char[]{'?'});
							if (siteMapNodeKeyArr.Length == 2)
							{
								siteMapNodeKey = siteMapNodeKeyArr[0];
								queryStringToAppend = ruleEnum.Current.MatchExpressionRegex.Replace(app.Request.RawUrl, siteMapNodeKeyArr[1]);
								queryStringToAppend = queryStringToAppend.Replace("?", "&");
							}
						}
						SiteMapNode foundNode = CurrentSiteMapProvider.FindSiteMapNodeFromKey(siteMapNodeKey);
						if (foundNode == null)
						{
							throw new ConfigurationErrorsException(StringAide.FormatInvariant(Resources.UrlRewriter.SiteMapNodeNotFoundForRule, ruleEnum.Current.Id, ruleEnum.Current.Destination));
						}
						HttpContext.Current.Items["DNAideCurrentSiteMapNode"] = foundNode;
						return CheckCurrentSiteMapNode(foundNode, queryStringToAppend);
					}
					else
					{
						finalDestination = ruleEnum.Current.MatchExpressionRegex.Replace(app.Request.RawUrl, UrlAide.Resolve(ruleEnum.Current.Destination));
					}

					if (ruleEnum.Current.RuleType == UrlRewriterRuleType.Redirect)
					{
						HttpContext.Current.Response.StatusCode = ruleEnum.Current.RedirectStatusCode;
						HttpContext.Current.Response.AddHeader("Location", finalDestination);
						HttpContext.Current.Response.End();
					}
					else
					{
						HttpContext.Current.RewritePath(finalDestination);
					}
					// Job done
					return true;

				}
			}
			return false;
		}

		private static bool CheckCurrentSiteMapNode()
		{
			if (CurrentSiteMapProvider.CurrentNode == null)
			{
				return false;
			}
			return CheckCurrentSiteMapNode(CurrentSiteMapProvider.CurrentNode);
		}


		private static bool CheckCurrentSiteMapNode(SiteMapNode siteMapNode)
		{
			return CheckCurrentSiteMapNode(siteMapNode, null);
		}

		private static bool CheckCurrentSiteMapNode(SiteMapNode siteMapNode, string queryStringToAppend)
		{
			if (siteMapNode == null)
			{
				return false;
			}

			SiteMapNode currentNode = siteMapNode;

			int redirectStatusCode = CastAide.AsInt32(currentNode["redirectStatusCode"], 301);

			string finalDestination = string.Empty;

			// Redirect to another URL or sitemapnode
			if (currentNode["redirectToUrl"] != null)
			{
				finalDestination = UrlAide.Resolve(currentNode["redirectToUrl"]);
			}
			else if (currentNode["redirectToSiteMapNodeKey"] != null)
			{
				// Redirecting to the Url of another SiteMapNode key
				SiteMapNode foundNode = CurrentSiteMapProvider.FindSiteMapNodeFromKey(currentNode["redirectToSiteMapNodeKey"]);
				if (foundNode == null)
				{
					throw new ConfigurationErrorsException(StringAide.FormatInvariant(Resources.UrlRewriter.SiteMapNodeNotFoundForSiteMapNodeRule, currentNode.Key, currentNode["redirectToSiteMapNodeKey"]));
				}
				finalDestination = UrlAide.Resolve(foundNode.Url);
			}

			if (finalDestination.Length > 0)
			{
				HttpContext.Current.Response.StatusCode = redirectStatusCode;
				HttpContext.Current.Response.AddHeader("Location", finalDestination);
				HttpContext.Current.Response.End();
				return true;
			}


			// Rewrite to a page (typically an aspx page handling the presentation
			if (currentNode["rewriteToUrl"] != null)
			{
				finalDestination = currentNode["rewriteToUrl"];
			}
			if (finalDestination.Length < 1)
			{
				finalDestination = DNAideConfigurationSettings.Web.UrlRewriter.DefaultSiteMapNodeRewriteUrl;
			}
			if (finalDestination.Length > 0)
			{
				if (!string.IsNullOrEmpty(queryStringToAppend))
				{
					finalDestination += "?" + queryStringToAppend;
				}
				HttpContext.Current.Items["DNAideQueryString"] = string.IsNullOrEmpty(queryStringToAppend) ? null : queryStringToAppend;
				HttpContext.Current.RewritePath(finalDestination);
				return true;
			}

			return false;
		}

		


		#region Event Handlers

		private void Application_AuthorizeRequest(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;
			HandleRequest(app);
		}


		private void Application_AuthenticateRequest(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;
			HandleRequest(app);
		}


		private void Application_BeginRequest(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;
			HandleRequest(app);
		}

		private void Application_PreRequestHandlerExecute(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;
			System.Web.UI.Page page = app.Context.CurrentHandler as System.Web.UI.Page;
			if (page != null)
			{
				page.PreInit += new EventHandler(Application_PagePreInit);
			}
		}

		private static void Application_PagePreInit(object sender, EventArgs e)
		{
			string queryString = HttpContext.Current.Items["DNAideQueryString"] as string;
			string rawUrl = HttpContext.Current.Request.RawUrl;

			if (queryString != null)
			{
				// We must have had a query string so append it back on again
				int index = rawUrl.IndexOf("?");
				if (index != -1)
				{
					rawUrl = rawUrl.Substring(0, index);
					rawUrl = string.Format("{0}?{1}", rawUrl, HttpContext.Current.Server.HtmlDecode(queryString));
				}
			}

			// Rewrite back again so that postback works and doesn't postback to the underlying template.
			// It will include the query string params though
			HttpContext.Current.RewritePath(rawUrl);
		}


		#endregion


	}
}
